﻿using SQ.BLL.Factory;
using SQ.BLL.Interfaces;
using SQ.Core.Configuration.Calculadora;
using SQ.Core.Configuration.Framework;
using SQ.Core.FilaPoll.Interfaces;
using SQ.Core.FilaProcessor.Interface;
using SQ.Domain;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace SQ.Core.FilaPoll
{
    public class FilaPoll : IFilaPoll
    {
        private FrameworkConfig frameworkConfig;
        private IFilaBS filaBS;
        private IFilaProcessor processadorFila;

        public FilaPoll(FrameworkConfig frameworkConfig_, IFilaProcessor filaProcessor_)
        {
            this.frameworkConfig = frameworkConfig_;
            this.filaBS = BLLFactory.getFilaBS();
            this.processadorFila = filaProcessor_;
            ConfigurarThreadPool();
        }

        private void ConfigurarThreadPool()
        {
            ThreadPool.SetMaxThreads(this.frameworkConfig.NumeroTreadsDisponiveis, this.frameworkConfig.NumeroTreadsDisponiveis);
        }

        public void Polling()
        {
            while (true)
            {
                ProcessarFilas();

                AguardarThreadLivre();

                AguardarProximaExecucao();
            }
        }

        private void ProcessarFilas()
        {
            try
            {
                var listaFilaParaProcessamento = this.filaBS.GetFilasAbertas();

                if (listaFilaParaProcessamento != null)
                {
                    foreach (var filaParaProcessamento in listaFilaParaProcessamento)
                    {
                        if (this.frameworkConfig.IsProcessamentoParalelizado)
                        {
                            ThreadPool.QueueUserWorkItem(new WaitCallback(ProcessarFila), filaParaProcessamento);
                        }
                        else
                        {
                            this.processadorFila.CalcularTempoMedioFila((Fila)filaParaProcessamento);
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine("ERRO: {0}", ex.Message);
            }
        }

        private void ProcessarFila(object fila)
        {
            try
            {
                Thread.CurrentThread.Name = "Thread_" + Thread.CurrentThread.ManagedThreadId.ToString();
                this.processadorFila.CalcularTempoMedioFila((Fila)fila);
            }
            catch (Exception ex)
            {
                throw new Exception("Erro no processamento assincrono, motivo: " + ex.Message);
            }
        }

        private void AguardarThreadLivre()
        {
            if (this.frameworkConfig.IsProcessamentoParalelizado)
            {
                int threadsDisponiveis = 0;
                int ioThreadsDisponiveis = 0;

                ThreadPool.GetAvailableThreads(out threadsDisponiveis, out ioThreadsDisponiveis);

                while (threadsDisponiveis == 0 || ioThreadsDisponiveis == 0)
                {
                    Thread.Sleep(1000);
                    ThreadPool.GetAvailableThreads(out threadsDisponiveis, out ioThreadsDisponiveis);
                }
            }
        }

        private void AguardarProximaExecucao()
        {
            Thread.Sleep(this.frameworkConfig.TempoPausaProcessamento);
        }
    }
}
